2026_daft_punk_revolution_909_2.py

#

SPDX-FileCopyrightText: 2026 Salma Mouma SPDX-FileCopyrightText: 2026 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

import math
import os
import random

import bpy
#

SALMA MOUMA ---------------------- REVOLUTION 909 - DAFT PUNK Jury Final Juin 2026 NB : Unable Cell Fracture Add-On

#

---------------------------------------CLEARING-----------------------------------------#

os.system("clear")
#
def clean_that_up():
    bpy.ops.object.select_all(action="SELECT")

    bpy.data.objects["Oblique Camera"].select_set(False)

    bpy.ops.object.delete()
    bpy.ops.outliner.orphans_purge()


clean_that_up()
#

---------------------------------------CLEARING-----------------------------------------#

#

tout mettre dans la collection fracture_2

if "fracture_2" not in bpy.data.collections:
    fracture_coll = bpy.data.collections.new("fracture_2")
    bpy.context.scene.collection.children.link(fracture_coll)
else:
    fracture_coll = bpy.data.collections["fracture_2"]
#
DENSITE DES COUCHES
#

A : Creation du fracture cube ################################################################

bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=0.1)
obj = bpy.context.active_object
obj.name = "Fracture_Cube"
#

Subdivision

bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.subdivide(number_cuts=10)
bpy.ops.object.mode_set(mode="OBJECT")
#

B : Lancement du cell fracture

bpy.ops.object.add_fracture_cell_objects(source={"PARTICLE_OWN"})
#

on les stocke dans une liste

fractured_objects = [obj for obj in bpy.context.selected_objects if "cell" in obj.name]
#

C : Supprimer le fracture cube

if "Fracture_Cube" in bpy.data.objects:
    bpy.data.objects.remove(bpy.data.objects["Fracture_Cube"], do_unlink=True)
#

D : Randomizing

for block in fractured_objects:
#

Position random dans le cube de 10cm (-0.05m à 0.05m) + taille random

    s = random.uniform(0.9, 1)
    block.scale = (s, s, s)
#
def is_corner_fragment(obj, threshold=0.03):
    loc = obj.location
    return (
        abs(abs(loc.x) - 0.05) < threshold
        and abs(abs(loc.y) - 0.05) < threshold
        and abs(abs(loc.z) - 0.05) < threshold
    )
#
CORNERS (Forme) - Structure mélodique
#

filtrer les fragments NON coins

non_corner_blocks = [obj for obj in fractured_objects if not is_corner_fragment(obj)]
#

Randomizing le nombre à supprimer

nb_to_delete = min(random.randint(65, 95), len(non_corner_blocks))

for _ in range(nb_to_delete):
    block_to_remove = random.choice(non_corner_blocks)
    non_corner_blocks.remove(block_to_remove)
    fractured_objects.remove(block_to_remove)
    bpy.data.objects.remove(block_to_remove, do_unlink=True)
#

tout ranger dand la collection fracture_2

for obj in bpy.data.objects:
    name_low = obj.name.lower()
    if "cell" in name_low or "corner" in name_low:
        if obj.name not in fracture_coll.objects:
            fracture_coll.objects.link(obj)
#

On evite le doublage

        for coll in obj.users_collection:
            if coll != fracture_coll:
                coll.objects.unlink(obj)
#

Structure rythmique - GEOMETRY NODES

Objectif: structure physique qui tiens les fragments representants les couches

#

objet support

bpy.ops.mesh.primitive_cube_add(size=0.1, location=(0, 0, 0))
geo_obj = bpy.context.active_object
geo_obj.name = "GN_Structure"
#

Cet objet ne doit pas etre dans la meme collection que les autres

if geo_obj.name in bpy.data.collections["fracture_2"].objects:
    bpy.data.collections["fracture_2"].objects.unlink(geo_obj)
#

On le remet dans la collection principale si besoin

if geo_obj.name not in bpy.context.scene.collection.objects:
    bpy.context.scene.collection.objects.link(geo_obj)
#

modifier

mod = geo_obj.modifiers.new(name="GeometryNodes", type="NODES")
#

node tree - nom

node_group = bpy.data.node_groups.new("StructureNodes", "GeometryNodeTree")
mod.node_group = node_group

nodes = node_group.nodes
links = node_group.links
#

clear nodes

nodes.clear()
#

creer l OUTPUT - first step

node_group.interface.new_socket(
    name="Geometry",
    in_out="OUTPUT",
    socket_type="NodeSocketGeometry",
)
#

nodes

group_out = nodes.new("NodeGroupOutput")

collection_info = nodes.new("GeometryNodeCollectionInfo")
instances_to_points = nodes.new("GeometryNodeInstancesToPoints")

cube = nodes.new("GeometryNodeMeshCube")

proximity = nodes.new("GeometryNodeProximity")
set_position = nodes.new("GeometryNodeSetPosition")
merge = nodes.new("GeometryNodeMergeByDistance")

mesh_to_curve = nodes.new("GeometryNodeMeshToCurve")
curve_circle = nodes.new("GeometryNodeCurvePrimitiveCircle")
curve_to_mesh = nodes.new("GeometryNodeCurveToMesh")
#

paramètres de ces nodes

collection_info.inputs["Collection"].default_value = bpy.data.collections["fracture_2"]
collection_info.transform_space = "RELATIVE"
collection_info.inputs["Separate Children"].default_value = True

cube.inputs["Size"].default_value = (0.1, 0.1, 0.1)
cube.inputs[0].default_value = (0.1, 0.1, 0.1)  # Size
cube.inputs[1].default_value = 100  # Vertices X
cube.inputs[2].default_value = 100  # Vertices Y
cube.inputs[3].default_value = 100  # Vertices Z

curve_circle.inputs["Radius"].default_value = 0.0015
curve_circle.inputs["Resolution"].default_value = 40

merge.inputs["Distance"].default_value = 0.01

proximity.target_element = "POINTS"
#

connexions entre ces nodes

#

instances - points

links.new(collection_info.outputs["Instances"], instances_to_points.inputs["Instances"])
#

proximity

links.new(instances_to_points.outputs[0], proximity.inputs[0])
#

cube - base géométrique

links.new(cube.outputs["Mesh"], set_position.inputs["Geometry"])
#

position controlee par proximity

links.new(proximity.outputs["Position"], set_position.inputs["Position"])
#

merge

links.new(set_position.outputs["Geometry"], merge.inputs["Geometry"])
#

mesh - curve

links.new(merge.outputs["Geometry"], mesh_to_curve.inputs["Mesh"])
#

curve - mesh

links.new(mesh_to_curve.outputs["Curve"], curve_to_mesh.inputs["Curve"])
links.new(curve_circle.outputs["Curve"], curve_to_mesh.inputs["Profile Curve"])
#

sortie finale

links.new(curve_to_mesh.outputs["Mesh"], group_out.inputs["Geometry"])
#

UPDATE

bpy.context.view_layer.update()
#

LOOPS - REPETITIVITE

#

Le code décide aléatoirement si les loops apparaissent ou non (moment de break ou pas)

show_loops = random.choice([True, False])

if show_loops:
#

Creation du cadre

    bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0))
    cadre_base = bpy.context.active_object
    cadre_base.name = "Cadre"
    cadre_base.scale = (0.07, 0.07, 0.005)
    bpy.ops.object.transform_apply(scale=True)
#

Cutter cadre pour percer

    bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0))
    cutter = bpy.context.active_object
    cutter.scale = (0.05, 0.05, 0.1)
    bpy.ops.object.transform_apply(scale=True)
#

Boolean

    bool_mod = cadre_base.modifiers.new(name="Perçage", type="BOOLEAN")
    bool_mod.object = cutter
    bool_mod.operation = "DIFFERENCE"
    bool_mod.solver = "EXACT"

    bpy.context.view_layer.objects.active = cadre_base
    bpy.ops.object.modifier_apply(modifier="Perçage")
    bpy.data.objects.remove(cutter, do_unlink=True)
    cadre_base.hide_set(True)
#

rotation

    def rad(deg):
        return math.radians(deg)

    max_offset = 0.014
#

1 cadre X

    new_X = cadre_base.copy()
    new_X.data = cadre_base.data.copy()
    new_X.name = "Cadre_X"
    bpy.context.collection.objects.link(new_X)
    new_X.location = (random.uniform(-max_offset, max_offset), 0, 0)
    new_X.rotation_euler = (0, rad(90), 0)
#

2 cadre Y

    new_Y = cadre_base.copy()
    new_Y.data = cadre_base.data.copy()
    new_Y.name = "Cadre_Y"
    bpy.context.collection.objects.link(new_Y)
    new_Y.location = (0, random.uniform(-max_offset, max_offset), 0)
    new_Y.rotation_euler = (rad(90), 0, 0)
#

3 cadre Z

    new_Z = cadre_base.copy()
    new_Z.data = cadre_base.data.copy()
    new_Z.name = "Cadre_Z"
    bpy.context.collection.objects.link(new_Z)
    new_Z.location = (0, 0, random.uniform(-max_offset, max_offset))
    new_Z.rotation_euler = (0, 0, 0)
#

Nettoyage final seulement si cadre_base existe

    bpy.data.objects.remove(cadre_base, do_unlink=True)

else:
    print("Pas de Loops cette fois-ci !!")